package pers.chao.boxing.core;

import org.slf4j.Logger;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.WebUtils;
import pers.chao.tool.date.EasyDateFormat;
import pers.chao.tool.http.StatusCode;
import pers.chao.tool.http.StatusCodeException;
import pers.chao.tool.infrastructure.json.JacksonUtil;
import pers.chao.tool.infrastructure.log.BaseLogFactory;

import java.lang.annotation.Annotation;

/**
 * Description: 响应处理切面
 *
 * @author W.Chao
 * @date 2020/10/31 15:50
 **/
@RestControllerAdvice
@Order(1)
public class ResponseBoxingAspect implements ResponseBodyAdvice<Object> {
	private static final Class<? extends Annotation> RESPONSE_BOXING_CLASS = ResponseBoxing.class;
	private static final Class<? extends Annotation> RESPONSE_NON_BOXING_CLASS = ResponseNotBoxing.class;

	private static final Logger logger = BaseLogFactory.getLogger(ResponseBoxingAspect.class);

	private TimestampPlug datetimeProperties;

	public ResponseBoxingAspect(TimestampPlug datetimeProperties) {
		this.datetimeProperties = datetimeProperties;
	}

	/**
	 * 是否需要切面
	 *
	 * @param returnType
	 * @param clazz
	 * @return
	 */
	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> clazz) {
		return boxingSupports(returnType);
	}

	/**
	 * 切面执行方法，将数据、状态、信息封装为Response
	 *
	 * @param o
	 * @param methodParameter
	 * @param mediaType
	 * @param aClass
	 * @param serverHttpRequest
	 * @param serverHttpResponse
	 * @return
	 */
	@Override
	public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
								  Class<? extends HttpMessageConverter<?>> aClass,
								  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
		// 为了Response类的Jackson注解生效，因而使用Jackson序列化
		if (o == null) {
			// 当 o 返回类型为 string 并且为null会出现 java.lang.ClassCastException: Result cannot be cast to java.lang.String
			if (methodParameter.getParameterType().getName().equals("java.lang.String")) {
				return JacksonUtil.to(responseOrSetTimestamp(Response.success()));
			}
			return responseOrSetTimestamp(Response.success());
		}
		// 防止重复包装
		if (o instanceof Response) {
			return responseOrSetTimestamp((Response<?>) o);
		}
		// string 特殊处理 java.lang.ClassCastException: Result cannot be cast to java.lang.String
		if (o instanceof String) {
			return JacksonUtil.to(responseOrSetTimestamp(Response.success(o)));
		}

		return responseOrSetTimestamp(Response.success(o));
	}

	/**
	 * Response类的时间戳设置，没有配置时响应值不添加时间戳
	 *
	 * @param response
	 * @return
	 */
	private Response<?> responseOrSetTimestamp(Response<?> response) {
		if (datetimeProperties.getEnable()) {
			String now = EasyDateFormat.getNowyyyyMMddHHmmssSSS();
			String pattern = datetimeProperties.getPattern();
			// todo: 增加对Date类型的转换或long类型的时间毫秒数转换
			response.setDatetime(EasyDateFormat.format(now, pattern));
		}
		return response;
	}

	/**
	 * 检查是否支持响应包装
	 *
	 * @param returnType
	 * @return
	 */
	private boolean boxingSupports(MethodParameter returnType) {
		// 方法所在类或该方法有@ResponseNonBoxing修饰时不支持
		if (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), RESPONSE_NON_BOXING_CLASS)
				|| returnType.hasMethodAnnotation(RESPONSE_NON_BOXING_CLASS)) {
			return false;
		}
		// 方法所在类或该方法没有@ResponseBoxing修饰时不支持
		return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), RESPONSE_BOXING_CLASS)
				|| returnType.hasMethodAnnotation(RESPONSE_BOXING_CLASS);
	}

	/**
	 * 全局异常统一处理
	 *
	 * @param ex      捕获的异常
	 * @param request 当前请求
	 * @return
	 */
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Response<?>> exceptionHandler(Exception ex, WebRequest request) {
		logger.error("GlobalExceptionCatch..", ex);

		HttpHeaders httpHeaders = new HttpHeaders();
		// 处理用户的自定义异常
		if (ex instanceof StatusCodeException) {
			// 对于已知(即DefaultStatusCodeException的子类)，应该可以直接获取 StatusCode，异常直接打印在日志中
			StatusCodeException statusCodeException = (StatusCodeException) ex;
			// 抛出的异常信息体为异常携带的内容
			StatusCode statusCode = statusCodeException.getStatusCode();
			Response<?> body = Response.failure(statusCode != null ? statusCode : StatusCode.INTERNAL_SERVER_ERROR);

			return this.handleExceptionInternal(ex, body, httpHeaders, HttpStatus.OK, request);
		}

		return this.handleNormalException(ex, httpHeaders, request);
	}

	/**
	 * 包装异常
	 *
	 * @param ex
	 * @param httpHeaders
	 * @param request
	 * @return
	 */
	private ResponseEntity<Response<?>> handleNormalException(Exception ex, HttpHeaders httpHeaders, WebRequest request) {
		Response<?> body = Response.failure(StatusCode.INTERNAL_SERVER_ERROR);
		// 非自定义异常使用默认处理
		return this.handleExceptionInternal(ex, body, httpHeaders, HttpStatus.OK, request);
	}

	/**
	 * 处理自定义异常并包装
	 *
	 * @param ex
	 * @param body
	 * @param httpHeaders
	 * @param status
	 * @param request
	 * @return
	 */
	private ResponseEntity<Response<?>> handleExceptionInternal(
			Exception ex, Response<?> body, HttpHeaders httpHeaders, HttpStatus status, WebRequest request) {

		// TODO: 待观察
		request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
		return new ResponseEntity<>(body, httpHeaders, status);
	}

}
